# Original python / Omegle code samples stolen from http://pastebin.com/m67ea2891
# Original python / AIML code samples stolen from http://pyaiml.sourceforge.net/
# Original ALICE AIML files stolen from http://www.alicebot.org/aiml/aaa/
# ------------------------------------------------------------------------------------------------------------------------#
# CONSIDER THE FOLLOWING TEXT (INSIDE THIS ASCII BOX OF DOOM) EITHER A LICENSE OR INCOHERENT RANT. THE CHOICE IS YOURS.   #
# Everything else that doesn't appear in the above links, is mine. MINE. MINE. MINE.                                      #
#                                                                                                                         #
# But you can use it too, ok? Just this once.                                                                             #
# I still remember that leaf-blower you borrowed last summer.                                                             #
# It's cool though, this is just data anyways. I can always copy/paste more if I need to.                                 #
#                                                                                                                         #
# ------------------------------------------------------------------------------------------------------------------------#
# It should be further noted that some of the AIML files have been edited to fix
# issues with the pyAIML module. These issues were resolved on a case-by-case basis
# and I don't even remember exactly what they were... but changes were made. I promise.
#
# Any questions or inquiries may be directed to tizenkotoko@gmail.com
#

import urllib2 as url
import cPickle as pickle
import urllib,re,aiml,os,threading,time

# I'm not certain that a class is the most appropriate thing to have this contained in, but it feels right...
# Oh man, it feels so right...
class omegleClass():
    def __init__(self,ai):
        # check for a log directory, create it if need-be
        if not os.path.isdir('logs'): os.mkdir('logs')        
        # naming log files according to the time lets you run multiple bots from the same directory, without worrying about overlap.
        # if that's not a concern, you could just make it a static filename.
        logname = 'logs/pyOmegleALICE-%s.txt' % time.strftime('%m%d%Y-%H%M%S')
        
        # initialize a lot of class vars that will be needed later
        self.logfile = open(logname,'w')
        self.talkqueue1 = None
        self.talkqueue2 = None
        self.icebreak = True
        self.deadConvo = time.time() + 30
        self.appointment = False
        self.restart = False
        self.shutdown = False        
        # duration in seconds a person has to starting typing or speak before the bot disconnects        
        self.convoTimeout = 60        
        # do this so we wont have to pass it around to all the other functions that might need it
        self.ai = ai
        
        # this is the initial request for a random client. it returns a unique ID that we will use to communicate with "Stranger".
        site = url.urlopen('http://omegle.com/start','')
        siteread = site.read()        
        # we'll store the id, to use throughout this class
        self.id = siteread[1:len(siteread)-1]
        self.req = url.Request('http://omegle.com/events',urllib.urlencode({'id':self.id}))
        print('Connecting...')
        
        # now just cycle through this connection, fetching/parsing commands
        while True:
            site = url.urlopen(self.req)
            rec = site.read()            
            # gotta make sure the incoming command is safe for that eval down below...
            # even this method is probably not 100% secure
            # ... can't think of a better way to easily parse the [["djksal"],["jdksa"]] format raw commands though.
            vmsg = re.match('\[(.*)\]',rec)
            if vmsg:
                # moment of truth...
                msg = eval(rec)                
                # just verify it's a valid list...
                if type(msg) is list: # code made prettier thanks to nix64 (http://www.reddit.com/r/Python/comments/9a9kn/my_pythonomeglealice_aiml_mashup/c0c1l78)                    
                    # presto, iterate through that badboy...
                    for blk in msg:
                        cmd = blk[0]                        
                        # ternary operations are so sexy, amiright?
                        parm = None if len(blk) == 1 else blk[1]                        
                        # and start tossing the command processor some work
                        self.procCmd(cmd,parm)
                        
            # as we're cycling through received commands, check to see if we should stop
            if self.shutdown or self.restart: break
            
    # all communications with the "Stranger" go through here.
    # this is effectively a 2-slot queue and also controls
    # the threaded time-delay algorithm
    def talk1(self,txt):        
        # an appointment is set when the threaded timer is activated.
        if self.appointment:            
            # if there is text waiting to be "said", then add this current response to the queue.
            # the two-slot setup means that any pending responses will be overwritten by newer ones.
            # this is done on purpose, to prevent flooding.
            self.talkqueue2 = txt
            
        else:            
            # if there are no appointments, we are clear to schedule a time-delayed response.
            # add the response to the first queue slot
            self.talkqueue1 = txt            
            # let the "Stranger" know we are "typing" =p
            typing = url.urlopen('http://omegle.com/typing', '&id='+self.id)
            typing.close()            
            # flag us as having an appointment now
            self.appointment = True            
            # and fire off the timer.
            # this algorithm simulates just over 5 typed characters per second. This is moderately fast.
            # a slower typer should be set to .3
            # keep in mind though, the bot's verbose responses can cause some impatient people to disconnect...
            t = threading.Timer(len(txt) * .22,self.talk2)
            t.start()
            
    # when the talk1 timer expires, this function gets called
    def talk2(self):
        # clear appointments
        self.appointment = False        
        # make sure we didn't get called by a zombie thread...
        if self.shutdown or self.restart: return        
        # pull the message from the queue
        txt = self.talkqueue1        
        # the hardPrint function will log AND display whatever it is sent.
        # this particular line outputs something like:
        # 05:13.49 You: Hello there.
        self.hardPrint('%s You: %s' % (time.strftime('%H:%M.%S'),str(txt)))        
        # send the message to "Stranger"
        msgReq = url.urlopen('http://omegle.com/send', '&msg='+str(txt)+'&id='+self.id)
        msgReq.close()        
        # clear the queue1 slot
        self.talkqueue1 = None
        
        # something waiting in the 2nd slot?
        if self.talkqueue2:
            # pull it
            txt = self.talkqueue2            
            # clear the queue
            self.talkqueue2 = None            
            # fire that sucka back to talk1, so it can schedule an appointment.
            self.talk1(txt)
            
    # This is called 10 seconds after an initial chat session is established.
    # However, if self.icebreak isn't set anymore, or this is called by a zombie thread
    # then gtfo
    def breakIce(self):
        if not self.icebreak or self.shutdown or self.restart: return
        # Try to get this party started.
        self.talk1('Hello?')
        
    # This gets called periodically to check if the conversation has died.
    def deadConvoChk(self):
        # Don't bother if we're called from a zombie timer.
        if self.restart or self.shutdown: return        
        # See if the timer has expired.
        if self.deadConvo <= time.time():
            # Flag us for restarting
            print('Conversation died.')
            self.restart = True
        # If not, check back in soon
        else:
            t = threading.Timer(1,self.deadConvoChk)
            t.start()
            
    # hardPrint, as previously stated, logs and displays whatever text it is sent.
    def hardPrint(self,txt):
        # not sure how portable the \n line terminator is, but it works fine in notepad.
        self.logfile.write('%s\n' % txt)
        # as an added bonus, it will flush the log each time, so you can monitor the text files live.
        self.logfile.flush()
        # if this next line needs explaining, why are you even here?
        # consequently, do excessive comments get on your nerves?
        # and if so... why are you even here?
        print(txt)
        
    # if a programmer were to insist on doing things the hard way...
    # this next definition would contain the bulk of the source code.
    def procCmd(self,cmd,parm):
        # lol i dunno...
        if cmd == 'waiting':
            print('Waiting...')

        # received when "Stranger" disconnects. crazy huh?
        elif cmd == 'strangerDisconnected':
            self.hardPrint('%s Disconnected. (%s)' % (time.strftime('%H:%M.%S'),self.id))
            # this will force the bot to retry a new chat
            # you could also use:
            # self.shutdown = True
            # to force the script to stop completely.
            self.restart = True
            
        # I'm just going to stop commenting these. This protocol is pretty damn transparent.
        elif cmd == 'connected':
            # See if anything has happened, in 10 seconds.
            t1 = threading.Timer(10,self.breakIce)
            t1.start()
            # Start the dead conversation checker, which in-turn will restart itself as a threaded timer.
            self.deadConvoChk()
            self.hardPrint('%s Connected. (%s)' % (time.strftime('%H:%M.%S'),self.id))
            #self.talk1(self.ai.respond('VERSIONGET',self.id)) # This is an optional greeting
            
        elif cmd == 'typing':
            # reset the dead conversation timer.
            self.deadConvo = time.time() + self.convoTimeout
            # turn off the icebreaker flag
            if self.icebreak: self.icebreak = False
            #print('Incoming...') # More verbose, uncomment if you prefer.
            
        elif cmd == 'gotMessage':
            # reset the dead conversation timer.
            self.deadConvo = time.time() + self.convoTimeout
            self.hardPrint('%s Stranger: %s' % (time.strftime('%H:%M.%S'),parm))
            # a common question that gets asked on omegle is "m/f"
            # unfortunately I can't figure out how to trap the "/" character in AIML
            # so we're going to apply a hack here to work with that limitation.
            mf = parm.lower().split('/')
            if len(mf) == 2 and 'm' in mf and 'f' in mf: parm = 'm or f'
            # store the response in a var...
            r = self.ai.respond(parm,self.id)
            # then check it to make sure it didn't come back null, and shuttle it off to talk1!
            if len(r) > 0: self.talk1(r)

# most of this function was rewritten / expanded to accomodate my release of this source.
# It took me quite a bit of prodding and hair pulling before I realized that the bot predicates
# used in AIML have to be set from within the interpreter itself!

# these are some default settings that may or may not actually be referenced from the existing AIML files.
# once you set them for the first time, they will be dumped to a pickle file and reloaded from then on.
def initAI(ai):
    # I've discovered that all of these are used in Bot.aiml
    # Attempts will be made to clean that up, and make it more flexible in regards to
    # wether the bot is pretending to be human, or is "obvious" about being a bot.
    defProps = ['baseballteam','favoritesong', 'family', 'celebrities', 'feelings', 'phylum', 'president', 'looklike', 'orientation', 'birthplace', 'favoritefood', 'question', 'master', 'location', 'etype', 'friend', 'kingdom', 'favoriteauthor', 'footballteam', 'boyfriend', 'favoriteartist', 'name', 'favoritesport', 'gender', 'favoriteactor', 'celebrity', 'website', 'favoriteband', 'favoritebook', 'favoritecolor', 'sign', 'girlfriend', 'species', 'botmaster', 'forfun', 'favoriteactress', 'emotions', 'religion', 'hockeyteam', 'version', 'build', 'party', 'size', 'email', 'vocabulary', 'birthday', 'favoritemovie', 'nationality', 'ethics', 'friends', 'class', 'talkabout', 'language', 'age', 'kindmusic', 'genus', 'order', 'wear']
    f = 'settings.p'
    settings = {} # added to prevent reference before assignment exception
    
    # load the pickle if it exists
    if os.path.isfile(f):
        settings = pickle.load(open(f))
        print('Loaded %s.' % f)
        
    else:
        # if there's no pickle, offer to create the settings manually
        cmd = raw_input('Bot predicate settings not found. Manual entry? [y/N] ')
        if cmd.upper() == 'Y':
            settings = {}
            n, m = 1, len(defProps)
            # then just cycle through defProps and set the dictionary up
            for question in defProps:
                settings[question] = raw_input('[%d/%d] %s? ' % (n,m,question))
                n += 1
            # dump that mofo and you're good to go.
            pickle.dump(settings,open(f,'w'))
            print('Bot predicate settings saved to %s. Exclude this file if you redistribute this project.' % f)
            
        else:
            # seriously, not having *any* bot predicates is going to make your shit look all retarded.
            print('Warning: bot predicate settings do not exist. This will cause some responses to have blank spaces.')
            
    # if a settings dictionary exists, iterate through it
    for property in settings:
        # and set the predicates through the AIML class
        ai.setBotPredicate(property,settings[property])
        
    # set this to false here, to disable all the "WARNING: No match found" spam.
    ai.verbose(True)

# ah, the old reliable main()    
def main():
    # assign the AIML kernel and name the brain file.
    ai,brn = aiml.Kernel(),'pyOmegleALICE.brn'
    # offer to clear the brain file beforehand (effectively reloading the AIML)
    erase = raw_input('Erase brain file? [y/N] ')
    # debug mode will give you a direct interactive console with the bot
    # and allow you to jump out of it, in to a chat session or shut down completely.
    debug = raw_input('Debug? [y/N] ')
    
    # literal mindfucking right here, folks
    if erase.upper() == 'Y' and os.path.isfile(brn):
        print('Erasing brain...')
        os.remove(brn)
        
    # run that AI initialization function...
    initAI(ai)
    
    # attempt to load the brain file
    if os.path.isfile(brn):
        print('Brain file located. Loading...')
        ai.loadBrain(brn)
        print('Loaded.')
        
    # if not, reload all the AIML files and save the new brain.
    else:
        print('Brain file not found. Initializing...')
        ai.bootstrap(learnFiles='aiml/pyOmegleALICE.xml',commands='LOAD DSAJLKASDJIQIJELQNNCXNCAJKOSNBIABIDUBWUIQ')
        ai.saveBrain(brn)
        print('Initialized.')
        
    # enter the debug console if that was chosen
    # pretty basic.
    if debug.upper() == 'Y':
        print('Debug mode. Use QUIT/START to exit.')
        while True:
            inp = raw_input('> ')
            if inp.upper() in ('Q','QUIT'): return
            if inp.upper() in ('LOAD','START'): break            
            # small FYI, the parameter after the user input is a unique identifier that is used as a session token.
            # this allows for persistent iterations to occur between brain/kernel reloads.
            print(ai.respond(inp,'GOD'))
            
    # start the main program loop
    while True:
        # create a new omegle class
        cli = omegleClass(ai)
        if cli.shutdown: break
        # take back some of that cpu from the blazing infinite loop calculations we're doing.
        time.sleep(.1)
        
# this comment intentionally left blank.
if __name__ == '__main__':
    main()